/**
 * @license
 * Copyright 2019 Google LLC. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * =============================================================================
 */

import {memory, tensor3d, test_util} from '@tensorflow/tfjs-core';
import {describeBrowserEnvs, replaceHTMLVideoElementSource, setupFakeVideoStream} from '../util/test_utils';
import {WebcamIterator} from './webcam_iterator';

describeBrowserEnvs('WebcamIterator', () => {
  if (navigator.mediaDevices != null) {
    beforeEach(() => {
      setupFakeVideoStream();
    });

    it('create webcamIterator with html element', async () => {
      const videoElement = document.createElement('video');
      videoElement.width = 160;
      videoElement.height = 90;

      const webcamIterator = await WebcamIterator.create(videoElement);

      await replaceHTMLVideoElementSource(videoElement);

      const result = await webcamIterator.next();
      expect(result.done).toBeFalsy();
      expect(result.value.shape).toEqual([90, 160, 3]);
    });

    it('create webcamIterator with html element and capture', async () => {
      const videoElement = document.createElement('video');
      videoElement.width = 160;
      videoElement.height = 90;

      const webcamIterator = await WebcamIterator.create(videoElement);

      await replaceHTMLVideoElementSource(videoElement);

      const result = await webcamIterator.capture();
      expect(result.shape).toEqual([90, 160, 3]);
    });

    it('create webcamIterator with no html element', async () => {
      const webcamIterator = await WebcamIterator.create(
          null, {resizeWidth: 300, resizeHeight: 150});
      const result = await webcamIterator.next();
      expect(result.done).toBeFalsy();
      expect(result.value.shape).toEqual([150, 300, 3]);
    });

    it('create webcamIterator with no html element and capture', async () => {
      const webcamIterator = await WebcamIterator.create(
          null, {resizeWidth: 300, resizeHeight: 150});
      const result = await webcamIterator.capture();
      expect(result.shape).toEqual([150, 300, 3]);
    });

    it('create webcamIterator with no html element and no size', async done => {
      try {
        await WebcamIterator.create();
        done.fail();
      } catch (e) {
        expect(e.message).toEqual(
            'Please provide webcam video element, or resizeWidth and ' +
            'resizeHeight to create a hidden video element.');
        done();
      }
    });

    it('resize and center crop has correct shape with html element',
       async () => {
         const videoElement = document.createElement('video');
         videoElement.width = 100;
         videoElement.height = 200;
         const webcamIterator = await WebcamIterator.create(
             videoElement,
             {resizeWidth: 30, resizeHeight: 40, centerCrop: true});
         const result = await webcamIterator.next();
         expect(result.done).toBeFalsy();
         expect(result.value.shape).toEqual([40, 30, 3]);
       });

    it('resize and center crop has correct pixel with html element',
       async () => {
         const videoElement = document.createElement('video');
         videoElement.width = 4;
         videoElement.height = 4;
         const webcamIterator = await WebcamIterator.create(
             videoElement, {resizeWidth: 2, resizeHeight: 2, centerCrop: true});
         const originalImg = tensor3d([
           [[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]],
           [
             [1, 1, 1],
             [11, 12, 13],
             [14, 15, 16],
             [1, 1, 1],
           ],
           [
             [1, 1, 1],
             [1, 2, 3],
             [4, 5, 6],
             [1, 1, 1],
           ],
           [[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]]
         ]);
         const croppedImg = webcamIterator.cropAndResizeFrame(originalImg);
         test_util.expectArraysClose(
             await croppedImg.array(),
             await tensor3d([
               [[6.625, 7.1875, 7.75], [8.3125, 8.875, 9.4375]],

               [[1, 1.5625, 2.125], [2.6875, 3.25, 3.8125]]
             ]).array());
       });

    it('resize in bilinear method has correct shape with html element',
       async () => {
         const videoElement = document.createElement('video');
         videoElement.width = 100;
         videoElement.height = 200;

         const webcamIterator = await WebcamIterator.create(
             videoElement,
             {resizeWidth: 30, resizeHeight: 40, centerCrop: false});
         const result = await webcamIterator.next();
         expect(result.done).toBeFalsy();
         expect(result.value.shape).toEqual([40, 30, 3]);
       });

    it('resize in bilinear method has correct pixel with html element',
       async () => {
         const videoElement = document.createElement('video');
         videoElement.width = 4;
         videoElement.height = 4;
         const webcamIterator = await WebcamIterator.create(
             videoElement,
             {resizeWidth: 2, resizeHeight: 2, centerCrop: false});
         const originalImg = tensor3d([
           [[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]],
           [
             [1, 1, 1],
             [11, 12, 13],
             [14, 15, 16],
             [1, 1, 1],
           ],
           [
             [1, 1, 1],
             [1, 2, 3],
             [4, 5, 6],
             [1, 1, 1],
           ],
           [[1, 1, 1], [1, 1, 1], [1, 1, 1], [1, 1, 1]]
         ]);
         const croppedImg = webcamIterator.cropAndResizeFrame(originalImg);
         test_util.expectArraysClose(
             await croppedImg.array(), await tensor3d([
                                         [[1, 1, 1], [1, 1, 1]],

                                         [[1, 1, 1], [1, 1, 1]]
                                       ]).array());
       });

    it('webcamIterator could stop', async () => {
      const videoElement = document.createElement('video');
      videoElement.width = 160;
      videoElement.height = 90;

      const webcamIterator = await WebcamIterator.create(videoElement);
      await replaceHTMLVideoElementSource(videoElement);
      const result1 = await webcamIterator.next();
      expect(result1.done).toBeFalsy();
      expect(result1.value.shape).toEqual([90, 160, 3]);

      await webcamIterator.stop();
      const result2 = await webcamIterator.next();
      expect(result2.done).toBeTruthy();
      expect(result2.value).toBeNull();
    });

    it('webcamIterator could restart', async () => {
      const videoElement = document.createElement('video');
      videoElement.width = 160;
      videoElement.height = 90;

      const webcamIterator = await WebcamIterator.create(videoElement);
      await replaceHTMLVideoElementSource(videoElement);
      const result1 = await webcamIterator.next();
      expect(result1.done).toBeFalsy();
      expect(result1.value.shape).toEqual([90, 160, 3]);

      webcamIterator.stop();
      const result2 = await webcamIterator.next();
      expect(result2.done).toBeTruthy();
      expect(result2.value).toBeNull();

      // Skip validation when it's in Firefox and Mac OS, because BrowserStack
      // for Firefox does not trigger the readyState event when restarting.
      if (navigator.userAgent.search('Firefox') < 0 &&
          navigator.userAgent.search('OS X') < 0) {
        await webcamIterator.start();
        await replaceHTMLVideoElementSource(videoElement);
        const result3 = await webcamIterator.next();
        expect(result3.done).toBeFalsy();
        expect(result3.value.shape).toEqual([90, 160, 3]);
      }
    });

    it('capture with cropAndResize has no memory leaks', async () => {
      const videoElement = document.createElement('video');
      videoElement.width = 100;
      videoElement.height = 200;
      const webcamIterator = await WebcamIterator.create(
          videoElement, {resizeWidth: 30, resizeHeight: 40, centerCrop: true});

      const before = memory();

      const result = await webcamIterator.capture();
      result.dispose();

      const after = memory();

      expect(after.numTensors).toEqual(before.numTensors);
    });
  }
});
